import os
import subprocess
import glob
import xml.etree.ElementTree as ET
from collections import defaultdict
import json
import tempfile
import shutil

class CallGraphBuilder:
    """基于Doxygen的代码调用关系分析器"""
    
    def __init__(self, project_path):
        self.project_path = project_path
        self.call_graph = defaultdict(list)  # 函数调用了谁
        self.caller_graph = defaultdict(list)  # 谁调用了函数
        self.function_info = {}  # 存储函数信息
        
    def _create_doxyfile(self, output_dir):
        """创建临时Doxyfile配置文件"""
        doxyfile_content = f"""
PROJECT_NAME = "CodeAnalysis"
OUTPUT_DIRECTORY = "{output_dir}"
INPUT = "{self.project_path}"
FILE_PATTERNS = *.cc *.c *.cpp *.h *.hpp *.py
RECURSIVE = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
GENERATE_XML = YES
XML_OUTPUT = xml
GENERATE_HTML = NO
GENERATE_LATEX = NO
HAVE_DOT = YES
DOT_IMAGE_FORMAT = svg
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
DOT_TRANSPARENT = YES
"""
        doxyfile_path = os.path.join(output_dir, "Doxyfile")
        with open(doxyfile_path, "w") as f:
            f.write(doxyfile_content)
        return doxyfile_path

    def _run_doxygen(self, doxyfile_path):
        """运行Doxygen命令生成文档和XML"""
        try:
            print(f"正在运行Doxygen分析项目: {self.project_path}")
            result = subprocess.run(["doxygen", doxyfile_path], 
                           check=True, 
                           stdout=subprocess.PIPE, 
                           stderr=subprocess.PIPE)
            print("Doxygen分析完成")
            return True
        except subprocess.CalledProcessError as e:
            print(f"Doxygen运行失败: {e}")
            print(f"标准输出: {e.stdout.decode('utf-8')}")
            print(f"标准错误: {e.stderr.decode('utf-8')}")
            return False
        except FileNotFoundError:
            print("错误: 未找到Doxygen命令。请先安装Doxygen: http://www.doxygen.nl/")
            return False

    def _parse_doxygen_xml(self, xml_dir):
        """改进的XML解析函数，能够正确捕获.cc文件中实现的函数"""
        print("正在解析Doxygen XML文件...")
        
        # 查找所有的XML文件
        xml_files = glob.glob(os.path.join(xml_dir, "*.xml"))
        print(f"找到 {len(xml_files)} 个XML文件")
        
        # 统计文件类型
        implementation_stats = defaultdict(int)
        
        # 解析XML文件来获取函数和调用关系
        for xml_file in xml_files:
            if os.path.basename(xml_file) == "index.xml":
                continue
                    
            try:
                tree = ET.parse(xml_file)
                root = tree.getroot()
                
                # 处理编译单元(源文件)
                for compound in root.findall(".//compounddef"):
                    if compound.get("kind") in ["file", "class", "namespace", "struct"]:
                        # 获取主文件路径
                        file_path = None
                        location = compound.find(".//location")
                        if location is not None:
                            file_path = location.get("file")
                            if file_path and os.path.isabs(file_path):
                                file_path = os.path.relpath(file_path, self.project_path)
                        
                        if not file_path:
                            name = compound.find("compoundname")
                            if name is not None:
                                file_path = name.text
                        
                        if not file_path:
                            continue
                        
                        # 处理这个文件中的所有函数
                        for member in compound.findall(".//memberdef"):
                            # 支持所有类型的memberdef
                            kind = member.get("kind")
                            if kind in ["function", "method", "constructor", "destructor"]:
                                func_name = member.find("name").text
                                
                                # 检查函数声明和实现位置
                                location_element = member.find("location")
                                if location_element is None:
                                    continue
                                    
                                # 获取声明文件和实现文件
                                decl_file = location_element.get("file")
                                impl_file = location_element.get("bodyfile")
                                
                                # 记录实现文件的扩展名统计
                                if impl_file:
                                    _, ext = os.path.splitext(impl_file)
                                    implementation_stats[ext] += 1
                                
                                # 优先使用实现文件的路径
                                if impl_file:
                                    # 我们找到了.cc实现!
                                    if os.path.isabs(impl_file):
                                        impl_file = os.path.relpath(impl_file, self.project_path)
                                    file_path_for_func = impl_file
                                else:
                                    file_path_for_func = decl_file
                                    if os.path.isabs(file_path_for_func):
                                        file_path_for_func = os.path.relpath(file_path_for_func, self.project_path)
                                
                                # 创建更完整的函数名
                                full_name = f"{file_path_for_func}::{func_name}"
                                
                                # 创建函数信息
                                function_info = {
                                    'file': file_path_for_func,
                                    'decl_file': decl_file,
                                    'impl_file': impl_file,  # 可能为None
                                    'line': int(location_element.get("bodystart", location_element.get("line", 0))),
                                    'name': func_name,
                                    'kind': kind,
                                    'class': compound.get("kind") == "class" and compound.find("compoundname").text or None
                                }
                                
                                # 保存函数信息
                                self.function_info[full_name] = function_info
                                
                                # 处理函数的调用关系
                                for ref in member.findall(".//references"):
                                    ref_name = ref.text
                                    if ref_name and ref_name not in self.call_graph[full_name]:
                                        self.call_graph[full_name].append(ref_name)
                                
                                # 处理谁调用了这个函数
                                for ref in member.findall(".//referencedby"):
                                    caller_name = ref.text
                                    if caller_name and caller_name not in self.caller_graph[full_name]:
                                        self.caller_graph[full_name].append(caller_name)
                
            except ET.ParseError as e:
                print(f"解析XML文件 {xml_file} 时出错: {e}")
        
        # 打印实现文件类型统计
        print("\n函数实现文件类型统计:")
        for ext, count in implementation_stats.items():
            print(f"  - {ext}: {count}个函数")


    
    def build_full_graph(self):
            """使用Doxygen构建完整的调用关系树"""
            # 创建临时目录存储Doxygen输出
            with tempfile.TemporaryDirectory() as temp_dir:
                print(f"创建临时目录: {temp_dir}")
                
                # 创建Doxyfile
                doxyfile_path = self._create_doxyfile(temp_dir)
                
                # 运行Doxygen
                if not self._run_doxygen(doxyfile_path):
                    print("Doxygen分析失败，尝试使用备用方法")
                    return self._fallback_analysis()
                
                # 解析XML输出
                xml_dir = os.path.join(temp_dir, "xml")
                self._parse_doxygen_xml(xml_dir)
            print("解析XML:",xml_dir,"文件完成")
            
            # 打印分析结果
            print(f"分析完成: 找到 {len(self.function_info)} 个函数, "
                f"{sum(len(calls) for calls in self.call_graph.values())} 个调用关系")
            
            # 返回调用图数据
            return {
                'call_graph': dict(self.call_graph),
                'caller_graph': dict(self.caller_graph),
                'function_info': self.function_info
            }



    
    def _fallback_analysis(self):
        """当Doxygen失败时的备用分析方法"""
        print("使用简单分析代替Doxygen")
        
        # 查找所有C/C++文件
        cpp_files = []
        for ext in ['.c', '.cpp', '.cc', '.h', '.hpp']:
            cpp_files.extend(glob.glob(f"{self.project_path}/**/*{ext}", recursive=True))
        
        for file_path in cpp_files:
            rel_path = os.path.relpath(file_path, self.project_path)
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                try:
                    content = f.read()
                    # 简单的函数定义匹配
                    import re
                    func_pattern = re.compile(r'(\w+)\s+(\w+)\s*$[^)]*$\s*\{', re.MULTILINE)
                    for match in func_pattern.finditer(content):
                        func_name = match.group(2)
                        full_name = f"{rel_path}::{func_name}"
                        
                        # 添加函数信息
                        self.function_info[full_name] = {
                            'file': rel_path,
                            'line': content[:match.start()].count('\n') + 1,
                            'name': func_name
                        }
                except Exception as e:
                    print(f"分析文件 {file_path} 出错: {e}")
        
        return {
            'call_graph': dict(self.call_graph),
            'caller_graph': dict(self.caller_graph),
            'function_info': self.function_info
        }
    
    def to_json(self):
        """将调用图转换为JSON格式"""
        graph_data = self.build_full_graph()
        return json.dumps(graph_data, indent=2)
    
    def get_file_call_relationships(self, target_file):
        """获取特定文件的调用关系信息"""
        # 确保target_file是相对于项目根目录的路径
        if os.path.isabs(target_file):
            target_file = os.path.relpath(target_file, self.project_path)
        
        file_functions = []
        incoming_calls = []  # 谁调用了这个文件的函数
        outgoing_calls = []  # 这个文件的函数调用了谁
        
        # 获取文件中的所有函数
        for func_name, info in self.function_info.items():
            if info['file'] == target_file:
                # 添加函数信息
                func_info = {
                    'name': info['name'],
                    'class': info.get('class', None),
                    'callers': self.caller_graph.get(func_name, []),
                    'callees': self.call_graph.get(func_name, [])
                }
                file_functions.append(func_info)
                
                # 收集调用信息
                for caller in self.caller_graph.get(func_name, []):
                    if caller not in incoming_calls:
                        incoming_calls.append(caller)
                
                for callee in self.call_graph.get(func_name, []):
                    if callee not in outgoing_calls:
                        outgoing_calls.append(callee)
        
        return {
            'file': target_file,
            'functions': file_functions,
            'incoming_calls': incoming_calls,
            'outgoing_calls': outgoing_calls
        }
